package com.hanter.downloader.service;

import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.hanter.downloader.DebugLog;
import com.hanter.downloader.Downloader;
import com.hanter.downloader.RandomAccessSource;
import com.hanter.downloader.TaskInfo;
import com.hanter.downloader.exception.ResponseException;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpUrlConnectionService implements DownloadService {

    private static final String TAG = "HttpUrlConnection";

    private SSLContext sslContext;
    private TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {

        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
//            return new java.security.cert.X509Certificate[]{};
            return null;
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            Log.d(TAG, "checkClientTrusted");
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            Log.i(TAG, "checkServerTrusted");
        }
    }};

    @NonNull
    private volatile Downloader.Config config;

    public HttpUrlConnectionService(@NonNull Context context) {
        this(Downloader.get(context).getConfig());
    }

    public HttpUrlConnectionService(@NonNull Downloader.Config config) {
        this.config = config;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, null);
            HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(new TrustAllHostnameVerifier());
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void updateConfig(Downloader.Config config) {
        this.config = config;
    }

    @Override
    public TaskInfo getTaskInfo(@NonNull String url) {
        HttpURLConnection conn = null;
        try {
            conn = getRealConnection(HttpUtils.HEAD, url, -1L, -1L);
            if (conn == null) return null;

            ResponseWrapper resWrapper = ResponseWrapper.get(url, conn);
            if (resWrapper.getCode() == 200) {
                return TaskInfo.newTaskInfo(ResponseWrapper.get(url, conn));
            } else {
                return null;
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            return null;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    @Override
    public boolean isContentRangeSupported(@NonNull String url) {
        try {
            HttpURLConnection conn = getRealConnection(HttpUtils.HEAD, url, 0, -1);
            if (conn == null) {
                // FIXME 重新获取
                return false;
            }

            String contentRange = conn.getHeaderField("Content-Range");
            // 判断是否支持断点
            return !TextUtils.isEmpty(contentRange);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public long download(@NonNull Context context, @NonNull Uri uri, @NonNull String url, long start,
                         long end, @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        try (ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw")) {
            if (pfd == null)
                throw new FileNotFoundException("打开文件失败");

            return download(RandomAccessSource.get(pfd.getFileDescriptor()), url, start, end, listener);
        }
    }

    private long download(@NonNull RandomAccessSource ras, @NonNull String url, long start, long end,
                          @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        HttpURLConnection conn = null;
        InputStream is = null;
        try {
            conn = getRealConnection(url, start, end);

            if (conn == null) {
                throw new IOException("连接失败");
            }

            // FIXME 完善错误码处理
            if (conn.getResponseCode() != 206 && conn.getResponseCode() != 200) {
                throw new ResponseException(conn.getResponseCode(), conn.getResponseMessage());
            }

            if (start > 0)
                ras.seek(start);
            is = conn.getInputStream();
            byte[] buffer = new byte[config.getBufferSize()];
            int count = 0;
            int len = 0;

            if (listener != null) {
                listener.onIncrease(len);
            }

            while ((len = is.read(buffer, 0, buffer.length)) != -1) {
                if (Thread.currentThread().isInterrupted()) {
                    DebugLog.d(TAG, "interrupted");
                    throw new InterruptedException();
                }

                count += len;
                ras.write(buffer, 0, len);

                if (listener != null)
                    listener.onIncrease(len);
            }

            return count;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭 资源
            if (is != null) {
                try {
                    is.close();
                } catch (Exception ignored) {
                }
            }

            ras.close();

            if (conn != null)
                conn.disconnect();
        }

        // 下载任务
        return -1;
    }

    @Override
    public long download(@NonNull FileDescriptor fd, @NonNull String url, long start, long end,
                         @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        return download(RandomAccessSource.get(fd), url, start, end, listener);
    }

    @Override
    public long download(@NonNull File file, @NonNull String url, long start, long end,
                         @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException{
        return download(RandomAccessSource.get(file), url, start, end, listener);
    }

    @Override
    public long download(@NonNull File file, @NonNull String url, @Nullable OnProgressIncreaseListener listener) throws IOException, InterruptedException {
        return download(file, url, -1L, -1L, listener);
    }

    @Nullable
    private HttpURLConnection getRealConnection(@NonNull String url,
                                                long offset, long end) throws IOException, InterruptedException {
        return getRealConnection(HttpUtils.GET, url, offset, end);
    }

    @Nullable
    private HttpURLConnection getRealConnection(@NonNull String method, @NonNull String url,
                                                long start, long end) throws IOException, InterruptedException {
        HttpURLConnection conn = getHttpUrlConnection(method, url, start, end);
        if (conn == null)
            return null;

        int responseCode = conn.getResponseCode();
        // 判断是否重定向
        if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
                || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
            String redirectUrl = conn.getHeaderField("Location");
            // FIXME 更新重定向 URL
            // info.setFileUrl(redirectUrl);
            conn.disconnect();
            return getRealConnection(method, redirectUrl, start, end);
        }

        return conn;
    }

    private static class TrustAllHostnameVerifier implements HostnameVerifier {

        @SuppressLint("BadHostnameVerifier")
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    @Nullable
    private HttpURLConnection getHttpUrlConnection(@NonNull String method, @NonNull String url,
                                                   long start, long end) {
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod(method);
            conn.setConnectTimeout(config.getConnectTimeout());
            conn.setReadTimeout(config.getReadTimeOut());
            // 重定向
            conn.setInstanceFollowRedirects(true);
            // 不能使用缓存
            conn.setUseCaches(false);

            String range = HttpUtils.getRangeHeader(start, end);
            if (range != null) {
                conn.setRequestProperty("Range", range);
            }

            conn.connect();
        } catch (Exception e) {
            e.printStackTrace();
            if (conn != null) {
                conn.disconnect();
                conn = null;
            }
        }
        return conn;
    }
}
